package com.googlecode.carlosmonterocanabal.mojosprites.sprites;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.AbstractMap.SimpleEntry;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.imageio.ImageIO;

import org.apache.maven.plugin.logging.Log;

public class ImagePacker {

    private int padding;
    private int outputWidth, outputHeight;

    private Map<String, Point> imageSizes = new HashMap<String, Point>();
    private Map<String, Rectangle> imagePlacement = new HashMap<String, Rectangle>();

    public SimpleEntry<BufferedImage, Map<String, Rectangle>> packImage(
            final List<Entry<String, String>> imageKeyFiles, int maximumWidth, int maximumHeight, int imagePadding, Log log)
            throws IllegalArgumentException {
        outputWidth = maximumWidth;
        outputHeight = maximumHeight;
        padding = imagePadding;

        long begin;

        imageSizes.clear();
        imagePlacement.clear();


        // Obtener los tamanos de todas las imagenes 
        for (Entry<String, String> entry : imageKeyFiles) {

            BufferedImage readImage = null;
            try {
                readImage = ImageIO.read(new File(entry.getValue()));
                int h = readImage.getHeight();
                int w = readImage.getWidth();

                imageSizes.put(entry.getKey(), new Point(w, h));

            } catch (Exception e) {
                throw new IllegalArgumentException(e.getMessage(), e);
            }

        }

        Collections.sort(imageKeyFiles, new Comparator<Entry<String, String>>() {

            public int compare(Entry<String, String> o1, Entry<String, String> o2) {

                Point p1 = imageSizes.get(o1.getKey());
                Point p2 = imageSizes.get(o2.getKey());

                int c = -Integer.valueOf(p1.x).compareTo(Integer.valueOf(p2.x));
                if (c != 0) {
                    return c;
                }

                c = -Integer.valueOf(p1.y).compareTo(Integer.valueOf(p2.y));
                if (c != 0) {
                    return c;
                }

                return o1.getKey().compareTo(o2.getKey());
            }

        });

        log.info("Start packing images");
        begin = System.currentTimeMillis();
        if (!packImageRectangles(imageKeyFiles))
            throw new IllegalArgumentException("Error al general el sprite en la fase de empaquetado");

       log.info("Timing packing images: " + (System.currentTimeMillis() - begin) +" ms");
        begin = System.currentTimeMillis();
        BufferedImage outputBufferedImage = createOutputImage(imageKeyFiles);
        log.info("Timing create image spride: " + (System.currentTimeMillis() - begin));
        if (outputBufferedImage == null)
            throw new IllegalArgumentException("Error al general el sprite");

        String[] keys = new String[imagePlacement.entrySet().size()];
        int i = 0;
        for (Entry<String, Rectangle> key : imagePlacement.entrySet()) {
            keys[i++] = key.getKey();
        }

        for (String k : keys) {
            // get the actual size
            Point s = imageSizes.get(k);

            // get the placement rectangle
            Rectangle r = imagePlacement.get(k);

            // set the proper size
            r.width = s.x;
            r.height = s.y;

            // insert back into the dictionary
            imagePlacement.put(k, r);
        }

        Map<String, Rectangle> outputMap = new HashMap<String, Rectangle>();
        for (Entry<String, Rectangle> pair : imagePlacement.entrySet()) {
            outputMap.put(pair.getKey(), pair.getValue());
        }

        imageSizes.clear();
        imagePlacement.clear();

        return new SimpleEntry<BufferedImage, Map<String, Rectangle>>(outputBufferedImage, outputMap);
    }

    private boolean packImageRectangles(List<Entry<String, String>> imageKeyFiles) {
        // create a dictionary for our test image placements
        Map<String, Rectangle> testImagePlacement = new HashMap<String, Rectangle>();

        // get the size of our smallest image
        int smallestWidth = Integer.MAX_VALUE;
        int smallestHeight = Integer.MAX_VALUE;
        for (Point size : imageSizes.values()) {
            smallestWidth = Math.min(smallestWidth, size.x);
            smallestHeight = Math.min(smallestHeight, size.y);
        }

        // we need a couple values for testing
        int testWidth = outputWidth;
        int testHeight = outputHeight;

        boolean shrinkVertical = false;

        // just keep looping...
        int iterations = 0;
        // poner while(true)
        while (iterations < 1) {
            // make sure our test dictionary is empty
            testImagePlacement.clear();

            // try to pack the images into our current test size
            if (!testPackingImages(imageKeyFiles, testWidth, testHeight, testImagePlacement)) {
                // if that failed...

                // if we have no images in imagePlacement, i.e. we've never succeeded at PackImages,
                // show an error and return false since there is no way to fit the images into our
                // maximum size texture
                if (imagePlacement.size() == 0)
                    return false;

                // otherwise return true to use our last good results
                if (shrinkVertical)
                    return true;

                shrinkVertical = true;
                testWidth += smallestWidth + padding + padding;
                testHeight += smallestHeight + padding + padding;
                continue;
            }

            // clear the imagePlacement dictionary and add our test results in
            imagePlacement.clear();
            for (Entry<String, Rectangle> pair : testImagePlacement.entrySet())
                imagePlacement.put(pair.getKey(), pair.getValue());

            // figure out the smallest bitmap that will hold all the images
            testWidth = testHeight = 0;
            for (Entry<String, Rectangle> pair : imagePlacement.entrySet()) {
                testWidth = Math.max(testWidth, pair.getValue().x + pair.getValue().width);
                testHeight = Math.max(testHeight, pair.getValue().y + pair.getValue().height);
            }

            // subtract the extra padding on the right and bottom
            if (!shrinkVertical)
                testWidth -= padding;
            testHeight -= padding;

            // if the test results are the same as our last output results, we've reached an optimal size,
            // so we can just be done
            if (testWidth == outputWidth && testHeight == outputHeight) {
                if (shrinkVertical)
                    return true;

                shrinkVertical = true;
            }

            // save the test results as our last known good results
            outputWidth = testWidth;
            outputHeight = testHeight;

            // subtract the smallest image size out for the next test iteration
            if (!shrinkVertical)
                testWidth -= smallestWidth;
            testHeight -= smallestHeight;

        }
        return true;
    }

    private boolean testPackingImages(List<Entry<String, String>> files, int testWidth, int testHeight,
            Map<String, Rectangle> testImagePlacement) {
        // create the rectangle packer
        ArevaloRectanglePacker rectanglePacker = new ArevaloRectanglePacker(testWidth, testHeight);

        for (Entry<String, String> entry : files) {
            // get the bitmap for this file
            Point size = imageSizes.get(entry.getKey());

            // pack the image
            Point origin = rectanglePacker.tryPack(size.x + padding, size.y + padding);
            if (origin == null) {
                return false;
            }

            // add the destination rectangle to our dictionary
            testImagePlacement.put(entry.getKey(),
                    new Rectangle(origin.x, origin.y, size.x + padding, size.y + padding));
        }

        return true;
    }

    private BufferedImage createOutputImage(List<Entry<String, String>> files) {
        try {
            BufferedImage outputImage = new BufferedImage(outputWidth, outputHeight, BufferedImage.TYPE_INT_ARGB);

            Graphics g = outputImage.getGraphics();
            // draw all the images into the output image
            for (Entry<String, String> imageFileName : files) {
                Rectangle location = imagePlacement.get(imageFileName.getKey());
                BufferedImage bitmap = ImageIO.read(new File(imageFileName.getValue()));
                if (bitmap == null)
                    return null;

                g.drawImage(bitmap, location.x, location.y, bitmap.getWidth(), bitmap.getHeight(), null);
            }

            return outputImage;
        } catch (Exception e) {
            return null;
        }
    }
}
